/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-var-requires */
const express = require('express'),
    fs = require('fs'),
    bodyParser = require('body-parser'),
    multiparty = require('multiparty'),
    SparkMD5 = require('spark-md5')

/*-CREATE SERVER-*/
const app = express(),
    PORT = 8888,
    HOST = 'http://127.0.0.1',
    HOSTNAME = `${HOST}:${PORT}`
app.listen(PORT, () => {
    console.log(
        `THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT：${PORT}，YOU CAN VISIT：${HOSTNAME}`
    )
})

/*-中间件-*/
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*')
    req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next()
})
app.use(
    bodyParser.urlencoded({
        extended: false,
        limit: '1024mb'
    })
)

/*-API-*/
// 延迟函数
const delay = function delay(interval) {
    typeof interval !== 'number' ? (interval = 1000) : null
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, interval)
    })
}

// 检测文件是否存在
const exists = function exists(path) {
    return new Promise(resolve => {
        fs.access(path, fs.constants.F_OK, err => {
            if (err) {
                resolve(false)
                return
            }
            resolve(true)
        })
    })
}

// 创建文件并写入到指定的目录 & 返回客户端结果
const writeFile = function writeFile(res, path, file, filename, stream) {
    return new Promise((resolve, reject) => {
        if (stream) {
            try {
                let readStream = fs.createReadStream(file.path),
                    writeStream = fs.createWriteStream(path)
                readStream.pipe(writeStream)
                readStream.on('end', () => {
                    resolve()
                    fs.unlinkSync(file.path)
                    res.send({
                        code: 0,
                        codeText: 'upload success',
                        originalFilename: filename,
                        servicePath: path.replace(__dirname, HOSTNAME)
                    })
                })
            } catch (err) {
                reject(err)
                res.send({
                    code: 1,
                    codeText: err
                })
            }
            return
        }
        fs.writeFile(path, file, err => {
            if (err) {
                reject(err)
                res.send({
                    code: 1,
                    codeText: err
                })
                return
            }
            resolve()
            res.send({
                code: 0,
                codeText: 'upload success',
                originalFilename: filename,
                servicePath: path.replace(__dirname, HOSTNAME)
            })
        })
    })
}

// 基于multiparty插件实现文件上传处理 & form-data解析
const uploadDir = `${__dirname}/upload`
const multiparty_upload = function multiparty_upload(req, auto) {
    typeof auto !== 'boolean' ? (auto = false) : null
    let config = {
        maxFieldsSize: 200 * 1024 * 1024
    }
    if (auto) config.uploadDir = uploadDir
    return new Promise(async (resolve, reject) => {
        await delay()
        new multiparty.Form(config).parse(req, (err, fields, files) => {
            if (err) {
                reject(err)
                return
            }
            resolve({
                fields,
                files
            })
        })
    })
}

// 单文件上传处理「FORM-DATA」
app.post('/upload_single', async (req, res) => {
    try {
        let { files } = await multiparty_upload(req, true)
        let file = (files.file && files.file[0]) || {}
        res.send({
            code: 0,
            codeText: 'upload success',
            originalFilename: file.originalFilename,
            servicePath: file.path.replace(__dirname, HOSTNAME)
        })
    } catch (err) {
        res.send({
            code: 1,
            codeText: err
        })
    }
})

app.post('/upload_single_name', async (req, res) => {
    try {
        let { fields, files } = await multiparty_upload(req)
        let file = (files.file && files.file[0]) || {},
            filename = (fields.filename && fields.filename[0]) || '',
            path = `${uploadDir}/${filename}`,
            isExists = false
        // 检测是否存在
        isExists = await exists(path)
        if (isExists) {
            res.send({
                code: 0,
                codeText: 'file is exists',
                originalFilename: filename,
                servicePath: path.replace(__dirname, HOSTNAME)
            })
            return
        }
        writeFile(res, path, file, filename, true)
    } catch (err) {
        res.send({
            code: 1,
            codeText: err
        })
    }
})

// 单文件上传处理「BASE64」
app.post('/upload_single_base64', async (req, res) => {
    let file = req.body.file,
        filename = req.body.filename,
        spark = new SparkMD5.ArrayBuffer(),
        suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1],
        isExists = false,
        path
    file = decodeURIComponent(file)
    file = file.replace(/^data:image\/\w+;base64,/, '')
    file = Buffer.from(file, 'base64')
    spark.append(file)
    path = `${uploadDir}/${spark.end()}.${suffix}`
    await delay()
    // 检测是否存在
    isExists = await exists(path)
    if (isExists) {
        res.send({
            code: 0,
            codeText: 'file is exists',
            originalFilename: filename,
            servicePath: path.replace(__dirname, HOSTNAME)
        })
        return
    }
    writeFile(res, path, file, filename, false)
})

// 大文件切片上传 & 合并切片
const merge = function merge(HASH, count) {
    return new Promise(async (resolve, reject) => {
        let path = `${uploadDir}/${HASH}`,
            fileList = [],
            suffix,
            isExists
        isExists = await exists(path)
        if (!isExists) {
            reject('HASH path is not found!')
            return
        }
        fileList = fs.readdirSync(path)
        if (fileList.length < count) {
            reject('the slice has not been uploaded!')
            return
        }
        fileList
            .sort((a, b) => {
                let reg = /_(\d+)/
                return reg.exec(a)[1] - reg.exec(b)[1]
            })
            .forEach(item => {
                !suffix ? (suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1]) : null
                fs.appendFileSync(`${uploadDir}/${HASH}.${suffix}`, fs.readFileSync(`${path}/${item}`))
                fs.unlinkSync(`${path}/${item}`)
            })
        fs.rmdirSync(path)
        resolve({
            path: `${uploadDir}/${HASH}.${suffix}`,
            filename: `${HASH}.${suffix}`
        })
    })
}
app.post('/upload_chunk', async (req, res) => {
    try {
        let { fields, files } = await multiparty_upload(req)
        let file = (files.file && files.file[0]) || {},
            filename = (fields.filename && fields.filename[0]) || '',
            path = '',
            isExists = false
        // 创建存放切片的临时目录
        let [, HASH] = /^([^_]+)_(\d+)/.exec(filename)
        path = `${uploadDir}/${HASH}`
        !fs.existsSync(path) ? fs.mkdirSync(path) : null
        // 把切片存储到临时目录中
        path = `${uploadDir}/${HASH}/${filename}`
        isExists = await exists(path)
        if (isExists) {
            res.send({
                code: 0,
                codeText: 'file is exists',
                originalFilename: filename,
                servicePath: path.replace(__dirname, HOSTNAME)
            })
            return
        }
        writeFile(res, path, file, filename, true)
    } catch (err) {
        res.send({
            code: 1,
            codeText: err
        })
    }
})
app.post('/upload_merge', async (req, res) => {
    let { HASH, count } = req.body
    try {
        let { filename, path } = await merge(HASH, count)
        res.send({
            code: 0,
            codeText: 'merge success',
            originalFilename: filename,
            servicePath: path.replace(__dirname, HOSTNAME)
        })
    } catch (err) {
        res.send({
            code: 1,
            codeText: err
        })
    }
})
app.get('/upload_already', async (req, res) => {
    let { HASH } = req.query
    let path = `${uploadDir}/${HASH}`,
        fileList = []
    try {
        fileList = fs.readdirSync(path)
        fileList = fileList.sort((a, b) => {
            let reg = /_(\d+)/
            return reg.exec(a)[1] - reg.exec(b)[1]
        })
        res.send({
            code: 0,
            codeText: '',
            fileList: fileList
        })
    } catch (err) {
        res.send({
            code: 0,
            codeText: '',
            fileList: fileList
        })
    }
})

app.use(express.static('./'))
app.use((req, res) => {
    res.status(404)
    res.send('NOT FOUND!')
})
